/*
 * Copyright 2019 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef GrQuadUtils_DEFINED
#define GrQuadUtils_DEFINED

#include "src/base/SkVx.h"
#include "src/gpu/ganesh/geometry/GrQuad.h"

enum class GrQuadAAFlags;
enum class GrAA : bool;
enum class GrAAType : unsigned;
struct SkRect;

namespace GrQuadUtils {
// Resolve disagreements between the overall requested AA type and the per-edge quad AA flags.
// Both outAAType and outEdgeFlags will be updated.
void ResolveAAType(GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, const GrQuad &quad, GrAAType *outAAtype,
    GrQuadAAFlags *outEdgeFlags);

/* *
 * Clip the device vertices of 'quad' to be in front of the W = 0 plane (w/in epsilon). The
 * local coordinates will be updated to match the new clipped vertices. This returns the number
 * of clipped quads that need to be drawn: 0 if 'quad' was entirely behind the plane, 1 if
 * 'quad' did not need to be clipped or if 2 or 3 vertices were clipped, or 2 if 'quad' had one
 * vertex clipped (producing a pentagonal shape spanned by 'quad' and 'extraVertices').
 */
int ClipToW0(DrawQuad *quad, DrawQuad *extraVertices);

/* *
 * Crops quad to the provided device-space axis-aligned rectangle. If the intersection of this
 * quad (projected) and cropRect results in a quadrilateral, this returns true. If not, this
 * quad may be updated to be a smaller quad of the same type such that its intersection with
 * cropRect is visually the same. This function assumes that the 'quad' coordinates are finite.
 *
 * The provided edge flags are updated to reflect edges clipped by cropRect (toggling on or off
 * based on cropAA policy). If provided, the local coordinates will be updated to reflect the
 * updated device coordinates of this quad.
 *
 * If 'computeLocal' is false, the local coordinates in 'quad' will not be modified.
 */
bool CropToRect(const SkRect &cropRect, GrAA cropAA, DrawQuad *quad, bool computeLocal = true);

inline void Outset(const skvx::float4 &edgeDistances, GrQuad *quad);

bool WillUseHairline(const GrQuad &quad, GrAAType aaType, GrQuadAAFlags edgeFlags);

class TessellationHelper {
public:
    // Set the original device and (optional) local coordinates that are inset or outset
    // by the requested edge distances. Use nullptr if there are no local coordinates to update.
    // This assumes all device coordinates have been clipped to W > 0.
    void reset(const GrQuad &deviceQuad, const GrQuad *localQuad);

    // Calculates a new quadrilateral with edges parallel to the original except that they
    // have been moved inwards by edgeDistances (which should be positive). Distances are
    // ordered L, B, T, R to match CCW tristrip ordering of GrQuad vertices. Edges that are
    // not moved (i.e. distance == 0) will not be used in calculations and the corners will
    // remain on that edge.
    //
    // The per-vertex coverage will be returned. When the inset geometry does not collapse to
    // a point or line, this will be 1.0 for every vertex. When it does collapse, the per-vertex
    // coverages represent estimated pixel coverage to simulate drawing the subpixel-sized
    // original quad.
    //
    // Note: the edge distances are in device pixel units, so after rendering the new quad
    // edge's shortest distance to the original quad's edge would be equal to provided edge dist
    skvx::float4 inset(const skvx::float4 &edgeDistances, GrQuad *deviceInset, GrQuad *localInset);

    // Calculates a new quadrilateral that outsets the original edges by the given distances.
    // Other than moving edges outwards, this function is equivalent to inset(). If the exact
    // same edge distances are provided, certain internal computations can be reused across
    // consecutive calls to inset() and outset() (in any order).
    void outset(const skvx::float4 &edgeDistances, GrQuad *deviceOutset, GrQuad *localOutset);

    // Compute the edge equations of the original device space quad passed to 'reset()'. The
    // coefficients are stored per-edge in 'a', 'b', and 'c', such that ax + by + c = 0, and
    // a positive distance indicates the interior of the quad. Edges are ordered L, B, T, R,
    // matching edge distances passed to inset() and outset().
    void getEdgeEquations(skvx::float4 *a, skvx::float4 *b, skvx::float4 *c);

    // Compute the edge lengths of the original device space quad passed to 'reset()'. The
    // edge lengths are ordered LBTR to match distances passed to inset() and outset().
    skvx::float4 getEdgeLengths();

    // Determine if the original device space quad has vertices closer than 1px to its opposing
    // edges, without going through the full work of computing the insets (assuming that the
    // inset distances would be 0.5px).
    bool isSubpixel();

private:
    // NOTE: This struct is named 'EdgeVectors' because it holds a lot of cached calculations
    // pertaining to the edge vectors of the input quad, projected into 2D device coordinates.
    // While they are not direction vectors, this struct represents a convenient storage space
    // for the projected corners of the quad.
    struct EdgeVectors {
        // Projected corners (x/w and y/w); these are the 2D coordinates that determine the
        // actual edge direction vectors, dx, dy, and invLengths
        skvx::float4 fX2D, fY2D;
        // Normalized edge vectors of the device space quad, ordered L, B, T, R
        // (i.e. next_ccw(x) - x).
        skvx::float4 fDX, fDY;
        // Reciprocal of edge length of the device space quad, i.e. 1 / sqrt(dx*dx + dy*dy)
        skvx::float4 fInvLengths;
        // Theta represents the angle formed by the two edges connected at each corner.
        skvx::float4 fCosTheta;
        skvx::float4 fInvSinTheta; // 1 / sin(theta)

        void reset(const skvx::float4 &xs, const skvx::float4 &ys, const skvx::float4 &ws, GrQuad::Type quadType);
    };

    struct EdgeEquations {
        // a * x + b * y + c = 0; positive distance is inside the quad; ordered LBTR.
        skvx::float4 fA, fB, fC;

        void reset(const EdgeVectors &edgeVectors);

        skvx::float4 estimateCoverage(const skvx::float4 &x2d, const skvx::float4 &y2d) const;

        bool isSubpixel(const skvx::float4 &x2d, const skvx::float4 &y2d) const;

        // Outsets or insets 'x2d' and 'y2d' in place. To be used when the interior is very
        // small, edges are near parallel, or edges are very short/zero-length. Returns number
        // of effective vertices in the degenerate quad.
        int computeDegenerateQuad(const skvx::float4 &signedEdgeDistances, skvx::float4 *x2d, skvx::float4 *y2d,
            skvx::Vec<4, int32_t> *aaMask) const;
    };

    struct OutsetRequest {
        // Positive edge distances to move each edge of the quad. These distances represent the
        // shortest (perpendicular) distance between the original edge and the inset or outset
        // edge. If the distance is 0, then the edge will not move.
        skvx::float4 fEdgeDistances;
        // True if the new corners cannot be calculated by simply adding scaled edge vectors.
        // The quad may be degenerate because of the original geometry (near colinear edges), or
        // be because of the requested edge distances (collapse of inset, etc.)
        bool fInsetDegenerate;
        bool fOutsetDegenerate;

        void reset(const EdgeVectors &edgeVectors, GrQuad::Type quadType, const skvx::float4 &edgeDistances);
    };

    struct Vertices {
        // X, Y, and W coordinates in device space. If not perspective, w should be set to 1.f
        skvx::float4 fX, fY, fW;
        // U, V, and R coordinates representing local quad.
        // Ignored depending on uvrCount (0, 1, 2).
        skvx::float4 fU, fV, fR;
        int fUVRCount;

        void reset(const GrQuad &deviceQuad, const GrQuad *localQuad);

        void asGrQuads(GrQuad *deviceOut, GrQuad::Type deviceType, GrQuad *localOut, GrQuad::Type localType) const;

        // Update the device and optional local coordinates by moving the corners along their
        // edge vectors such that the new edges have moved 'signedEdgeDistances' from their
        // original lines. This should only be called if the 'edgeVectors' fInvSinTheta data is
        // numerically sound.
        void moveAlong(const EdgeVectors &edgeVectors, const skvx::float4 &signedEdgeDistances);

        // Update the device coordinates by deriving (x,y,w) that project to (x2d, y2d), with
        // optional local coordinates updated to match the new vertices. It is assumed that
        // 'mask' was respected when determining (x2d, y2d), but it is used to ensure that only
        // unmasked unprojected edge vectors are used when computing device and local coords.
        void moveTo(const skvx::float4 &x2d, const skvx::float4 &y2d, const skvx::Vec<4, int32_t> &mask);
    };

    Vertices fOriginal;
    EdgeVectors fEdgeVectors;
    GrQuad::Type fDeviceType;
    GrQuad::Type fLocalType;

    // Lazily computed as needed; use accessor functions instead of direct access.
    OutsetRequest fOutsetRequest;
    EdgeEquations fEdgeEquations;

    // Validity of Vertices/EdgeVectors (always true after first call to set()).
    bool fVerticesValid = false;
    // Validity of outset request (true after calling getOutsetRequest() until next set() call
    // or next inset/outset() with different edge distances).
    bool fOutsetRequestValid = false;
    // Validity of edge equations (true after calling getEdgeEquations() until next set() call).
    bool fEdgeEquationsValid = false;

    // The requested edge distances must be positive so that they can be reused between inset
    // and outset calls.
    const OutsetRequest &getOutsetRequest(const skvx::float4 &edgeDistances);
    const EdgeEquations &getEdgeEquations();

    // Outsets or insets 'vertices' by the given perpendicular 'signedEdgeDistances' (inset or
    // outset is determined implicitly by the sign of the distances).
    void adjustVertices(const skvx::float4 &signedEdgeDistances, Vertices *vertices);
    // Like adjustVertices() but handles empty edges, collapsed quads, numerical issues, and
    // returns the number of effective vertices in the adjusted shape.
    int adjustDegenerateVertices(const skvx::float4 &signedEdgeDistances, Vertices *vertices);

    friend int ClipToW0(DrawQuad *, DrawQuad *); // To reuse Vertices struct
};
} // namespace GrQuadUtils

void GrQuadUtils::Outset(const skvx::float4 &edgeDistances, GrQuad *quad)
{
    TessellationHelper outsetter;
    outsetter.reset(*quad, nullptr);
    outsetter.outset(edgeDistances, quad, nullptr);
}

#endif
